// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  CodeMirror.multiplexingMode = function(outer /*, others */) {
    // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects
    var others = Array.prototype.slice.call(arguments, 1);

    function indexOf(string, pattern, from, returnEnd) {
      if (typeof pattern == 'string') {
        var found = string.indexOf(pattern, from);
        return returnEnd && found > -1 ? found + pattern.length : found;
      }
      var m = pattern.exec(from ? string.slice(from) : string);
      return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1;
    }

    return {
      startState: function() {
        return {
          outer: CodeMirror.startState(outer),
          innerActive: null,
          inner: null,
        };
      },

      copyState: function(state) {
        return {
          outer: CodeMirror.copyState(outer, state.outer),
          innerActive: state.innerActive,
          inner:
            state.innerActive &&
            CodeMirror.copyState(state.innerActive.mode, state.inner),
        };
      },

      token: function(stream, state) {
        if (!state.innerActive) {
          var cutOff = Infinity,
            oldContent = stream.string;
          for (var i = 0; i < others.length; ++i) {
            var other = others[i];
            var found = indexOf(oldContent, other.open, stream.pos);
            if (found == stream.pos) {
              if (!other.parseDelimiters) stream.match(other.open);
              state.innerActive = other;
              state.inner = CodeMirror.startState(
                other.mode,
                outer.indent ? outer.indent(state.outer, '') : 0,
              );
              return (
                other.delimStyle &&
                other.delimStyle + ' ' + other.delimStyle + '-open'
              );
            } else if (found != -1 && found < cutOff) {
              cutOff = found;
            }
          }
          if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
          var outerToken = outer.token(stream, state.outer);
          if (cutOff != Infinity) stream.string = oldContent;
          return outerToken;
        } else {
          var curInner = state.innerActive,
            oldContent = stream.string;
          if (!curInner.close && stream.sol()) {
            state.innerActive = state.inner = null;
            return this.token(stream, state);
          }
          var found = curInner.close
            ? indexOf(
                oldContent,
                curInner.close,
                stream.pos,
                curInner.parseDelimiters,
              )
            : -1;
          if (found == stream.pos && !curInner.parseDelimiters) {
            stream.match(curInner.close);
            state.innerActive = state.inner = null;
            return (
              curInner.delimStyle &&
              curInner.delimStyle + ' ' + curInner.delimStyle + '-close'
            );
          }
          if (found > -1) stream.string = oldContent.slice(0, found);
          var innerToken = curInner.mode.token(stream, state.inner);
          if (found > -1) stream.string = oldContent;

          if (found == stream.pos && curInner.parseDelimiters)
            state.innerActive = state.inner = null;

          if (curInner.innerStyle) {
            if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle;
            else innerToken = curInner.innerStyle;
          }

          return innerToken;
        }
      },

      indent: function(state, textAfter) {
        var mode = state.innerActive ? state.innerActive.mode : outer;
        if (!mode.indent) return CodeMirror.Pass;
        return mode.indent(
          state.innerActive ? state.inner : state.outer,
          textAfter,
        );
      },

      blankLine: function(state) {
        var mode = state.innerActive ? state.innerActive.mode : outer;
        if (mode.blankLine) {
          mode.blankLine(state.innerActive ? state.inner : state.outer);
        }
        if (!state.innerActive) {
          for (var i = 0; i < others.length; ++i) {
            var other = others[i];
            if (other.open === '\n') {
              state.innerActive = other;
              state.inner = CodeMirror.startState(
                other.mode,
                mode.indent ? mode.indent(state.outer, '') : 0,
              );
            }
          }
        } else if (state.innerActive.close === '\n') {
          state.innerActive = state.inner = null;
        }
      },

      electricChars: outer.electricChars,

      innerMode: function(state) {
        return state.inner
          ? { state: state.inner, mode: state.innerActive.mode }
          : { state: state.outer, mode: outer };
      },
    };
  };
});
